/*
 * mbport_serial.h
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-1-20     nx      	   the first version
 *
 */

#include "mbport_serial.h"
#include "modbus_utils.h"

static mbport_serial_t mb_serial_tab[4];

mbport_serial_t *_mb_serial_get(void)
{
    mb_uint8_t idx;
    for (idx = 0; idx < 4; idx++)
    {
        if (mb_serial_tab[idx].magic != 0x1A2B)
        {
            mb_serial_tab[idx].magic = 0x1A2B;
            return &mb_serial_tab[idx];
        }
    }
    return MB_NULL;
}

void _mb_serial_put(mbport_serial_t *mb_serial)
{
    mb_serial->magic = 0;
}

static mb_int32_t _mbport_serial_open(modbus_port_t *mb_port)
{
    mbport_serial_t *mb_serial = (mbport_serial_t *)mb_port;


    mb_serial->handle = CreateFile(TEXT(mb_serial->name),
            GENERIC_READ | GENERIC_WRITE,   //Reading and writing are allowed
            0,                              //An exclusive way
            NULL,
            OPEN_EXISTING,                  //Open, not create
            0,                              //Synchronization mode
            NULL);

    if (mb_serial->handle == INVALID_HANDLE_VALUE)
    {
        MODBUS_DBG("Open %s fail.\n", mb_serial->name);
        return -MB_EIO;
    }

     /* Set the input buffer and output buffer sizes */
    SetupComm(mb_serial->handle, 1024 * 1024, 1024 * 1024);
    /* Set read timeout */
    mb_serial->comm_to.ReadIntervalTimeout = 1000;
    mb_serial->comm_to.ReadTotalTimeoutMultiplier = 500;
    mb_serial->comm_to.ReadTotalTimeoutConstant = 5000;
    /* Set write timeout */
    mb_serial->comm_to.WriteTotalTimeoutMultiplier = 2000;
    mb_serial->comm_to.WriteTotalTimeoutConstant = 2000;
    /* Set timeout */
    SetCommTimeouts(mb_serial->handle, &mb_serial->comm_to);

    SetCommState(mb_serial->handle, &mb_serial->dcb);
    /* Clear buffer */
    PurgeComm(mb_serial->handle, PURGE_TXCLEAR | PURGE_RXCLEAR);

    //MODBUS_DBG("Open %s success.\n", mb_serial->name);

    return MB_ENOERR;
}

static mb_int32_t _mbport_serial_close(modbus_port_t *mb_port)
{
    mb_int32_t ret;
    mbport_serial_t *mb_serial = (mbport_serial_t *)mb_port;

    if (CloseHandle(mb_serial->handle) == FALSE)
    {
        MODBUS_DBG("Close %s fail.\n", mb_serial->name);
        ret = -MB_EIO;
    }
    else
    {
        //MODBUS_DBG("Close %s success.\n", mb_serial->name);
        ret = MB_ENOERR;
    }

    return ret;
}

static mb_int32_t _mbport_serial_send(modbus_port_t *mb_port,
    mb_uint8_t *frame, mb_uint16_t length, mb_uint32_t timeout)
{
    DWORD n_bytes;
    mbport_serial_t *mb_serial = (mbport_serial_t *)mb_port;

    if (WriteFile(mb_serial->handle, (LPCVOID)frame, (DWORD)length, &n_bytes, NULL) != TRUE)
    {
        return 0;
    }

    return (mb_int32_t)n_bytes;
}

static mb_int32_t _mbport_serial_recv(modbus_port_t *mb_port,
    mb_uint8_t *frame, mb_uint16_t length, mb_uint32_t timeout)
{
    mb_int32_t ret;
    mb_uint16_t len;
    DWORD n_bytes;
    mbport_serial_t *mb_serial = (mbport_serial_t *)mb_port;

    if (frame == NULL)
    {
        ret = 0;
        goto err_exit;
    }

    len = 0;
    mb_serial->comm_to.ReadIntervalTimeout = timeout;
    mb_serial->comm_to.ReadTotalTimeoutMultiplier = 0;
    mb_serial->comm_to.ReadTotalTimeoutConstant = timeout;
    mb_serial->comm_to.WriteTotalTimeoutMultiplier = 0;
    mb_serial->comm_to.WriteTotalTimeoutConstant = 1000;
    SetCommTimeouts(mb_serial->handle, &mb_serial->comm_to);

    if (ReadFile(mb_serial->handle, (LPVOID)&frame[len++], 1,
            &n_bytes, NULL) != TRUE)
    {
        /* Some kind of error */
        ret = 0;
        goto err_exit;
    }

    if (n_bytes == 0)
    {
        /* Just timed out */
        ret = 0;
        goto err_exit;
    }

    mb_serial->comm_to.ReadIntervalTimeout = 50;
    mb_serial->comm_to.ReadTotalTimeoutMultiplier = 0;
    mb_serial->comm_to.ReadTotalTimeoutConstant = 50;
    mb_serial->comm_to.WriteTotalTimeoutMultiplier = 0;
    mb_serial->comm_to.WriteTotalTimeoutConstant = 1000;
    SetCommTimeouts(mb_serial->handle, &mb_serial->comm_to);

    while (len < length)
    {
        if (ReadFile(mb_serial->handle, (LPVOID)&frame[len], (DWORD)(length - len),
                &n_bytes, NULL) != TRUE)
        {
            /* Some kind of error */
            break;
        }

        if (n_bytes == 0)
        {
            break;
        }

        len += (mb_uint16_t)n_bytes;
    }

    ret = len;

err_exit:
    return ret;
}

static mb_int32_t _mbport_serial_control(modbus_port_t *mb_port, mb_uint32_t cmd, void *args)
{
    mbport_sercfg_t *sercfg = (mbport_sercfg_t *)args;
    mbport_serial_t *mb_serial = (mbport_serial_t *)mb_port;

    if (cmd == MODBUS_CTRL_READ)
    {

    }
    else if (cmd == MODBUS_CTRL_WRITE)
    {
        switch (sercfg->baud)
        {
            case 110:
                mb_serial->dcb.BaudRate = CBR_110;
                break;
            case 300:
                mb_serial->dcb.BaudRate = CBR_300;
                break;
            case 600:
                mb_serial->dcb.BaudRate = CBR_600;
                break;
            case 1200:
                mb_serial->dcb.BaudRate = CBR_1200;
                break;
            case 2400:
                mb_serial->dcb.BaudRate = CBR_2400;
                break;
            case 4800:
                mb_serial->dcb.BaudRate = CBR_4800;
                break;
            case 9600:
                mb_serial->dcb.BaudRate = CBR_9600;
                break;
            case 19200:
                mb_serial->dcb.BaudRate = CBR_19200;
                break;
            case 38400:
                mb_serial->dcb.BaudRate = CBR_38400;
                break;
            case 57600:
                mb_serial->dcb.BaudRate = CBR_57600;
                break;
            case 115200:
                mb_serial->dcb.BaudRate = CBR_115200;
                break;
            case 128000:
                mb_serial->dcb.BaudRate = CBR_128000;
                break;
            case 256000:
                mb_serial->dcb.BaudRate = CBR_256000;
                break;
            default:
                mb_serial->dcb.BaudRate = CBR_9600;
                break;
        }

        mb_serial->dcb.ByteSize = sercfg->data_bit;

        switch (sercfg->parity)
        {
            case 0:
                mb_serial->dcb.Parity = NOPARITY;
                break;
            case 1:
                mb_serial->dcb.Parity = ODDPARITY;
                break;
            case 2:
                mb_serial->dcb.Parity = EVENPARITY;
                break;
        }

        switch (sercfg->stop_bit)
        {
            case 1:
                mb_serial->dcb.StopBits = ONESTOPBIT;
                break;

            case 2:
                mb_serial->dcb.StopBits = TWOSTOPBITS;
                break;
        }

        mb_serial->ch_it = sercfg->ch_it;
    }

    return MB_ENOERR;
}


mbport_serial_t *mbport_serial_create(mb_uint8_t port_num)
{
    mbport_serial_t *mb_serial = MB_NULL;
    mbport_sercfg_t mb_sercfg = SERIAL_CONFIG_DEFAULT;

    mb_serial = _mb_serial_get();
    if (mb_serial == MB_NULL)
    {
        return MB_NULL;
    }

    modbus_memset(mb_serial->name, 0, 20);

    if (port_num <= 9)
    {
        sprintf(mb_serial->name, "COM%d", port_num);
    }
    else
    {
        sprintf(mb_serial->name, "\\\\.\\COM%d", port_num);
    }

    _mbport_serial_control((modbus_port_t *)mb_serial, MODBUS_CTRL_WRITE, &mb_sercfg);

    mb_serial->parent.open_fn = _mbport_serial_open;
    mb_serial->parent.close_fn = _mbport_serial_close;
    mb_serial->parent.send_fn = _mbport_serial_send;
    mb_serial->parent.recv_fn = _mbport_serial_recv;
    mb_serial->parent.ctrl_fn = _mbport_serial_control;

    return mb_serial;
}

void mbport_serial_destory(mbport_serial_t *mb_serial)
{
    _mb_serial_put(mb_serial);
}
